<!--
  文件上传的模态框

  用法：
    <qm-uploader-modal
      :title="scope.row.name"
      :bizType="scope.row.name"
      :fileType="scope.row.fileType"
      :fileSize="scope.row.fileSize"
      :dataKey="dataKey || 'qm-uploader-temp-data-key'"
      :ref="scope.row.uploaderRef"
      :fileList="scope.row.files"
      v-model="scope.row.uploaderVisible"
      @file-success="uploadSuccess"
      @cancel="scope.row.uploaderVisible = false"
    ></qm-uploader-modal>

  说明：
    title：模态框标题
    bizType：业务类型的名称
    fileType：允许上传的文件类型，不用带. ，多个用英文逗号隔开
    fileSize：允许上传的文件最大大小，单位MB
    dataKey：业务主键
    fileList：文件列表，上传成功的文件都会添加到这里
    v-model：绑定一个value值，控制模态框的显示隐藏
    @file-success：文件上传成功时触发该事件
    @cancel：关闭模态框后触发该事件

  其他相关参数可查看该组件源码
-->
<template>
  <vxe-modal
    ref="qmUploaderModal"
    v-model="visible"
    :width="width || 1024"
    :height="height || 600"
    :title="title"
    show-zoom
    @close="cancel"
  >
    <uploader
      ref="uploader"
      :options="opts"
      class="qm-uploader"
      :auto-start="autoStart"
      :fileStatusText="fileStatusText"
      @file-added="beforeUpload"
      @file-success="handleSuccess"
    >
      <el-empty
        v-if="!(Array.isArray(uploaderFiles) && uploaderFiles.length > 0)"
        description="请选择文件上传"
        style="flex: 1"
      />
      <uploader-list v-else class="qm-uploader-list" />

      <el-row>
        <el-col :span="12">
          <span v-show="fileType" style="color: red; line-height: 36px"
            >只允许上传 {{ fileType }} 格式的文件</span
          >
        </el-col>
        <el-col :span="12" style="text-align: right">
          <uploader-btn class="qm-uploader-select-btn">选择文件</uploader-btn>
          <el-button v-if="showUploadBtn" @click="allUpload"
            >开始上传</el-button
          >
          <slot name="moreBtn" />
        </el-col>
      </el-row>
    </uploader>
  </vxe-modal>
</template>

<script>
import { getCsrfToken } from "@/utils/auth";
import _ from "lodash";
import SparkMD5 from "spark-md5";
import request from "@/utils/request";

export default {
  name: "QmUploaderModal",
  props: {
    fileList: {
      type: Array,
      default: () => [],
    },
    dataKey: {
      type: String,
    },
    title: {
      type: String,
      default: "附件上传",
    },
    type: {
      type: String,
      default: "attach",
    },
    bizType: String,
    fileType: String,
    fileSize: Number,
    value: Boolean,
    width: Number,
    height: Number,
    limit: Number,
    autoStart: {
      type: Boolean,
      default: false,
    },
    /**
     * 如果未传url，默认使用 /attachment/chunkUpload2，该接口已实现分片上传功能.
     * 如果自己传入url，需考虑：
     * 1.该url是否已实现分片上传相关操作.
     *    1.1：未实现：可通过调节 assignUrlChunkSize 来规避 uploader 组件分片上传.
     *    1.2：已实现：
     *      1.2.1：可调节 assignUrlTestChunks 分块大小，或开启 assignUrlTestChunks 块校验等操作.
     */
    url: String,
    // 额外的上传参数
    query: {
      type: Object,
      default: () => {},
    },
    headers: {
      type: Object,
      default: () => {},
    },
    showUploadBtn: {
      type: Boolean,
      default: true,
    },
    // 每个分块大小，默认为2MB
    chunkSize: {
      type: Number,
      default: 2 * 1024 * 1024,
    },
    /**
     * 指定url时，每个分块大小.
     *
     * 注：如果传入的url未实现分片上传，可通过
     * 调节 assignUrlChunkSize 来规避 uploader 组件分片上传.
     */
    assignUrlChunkSize: {
      type: Number,
      default: 2048000000,
    },
    /**
     * 指定url时，是否开启分块校验(向服务器发出 GET 请求，校验是否已存在).
     *
     * 注：如果传入的url已实现分片上传，可将 assignUrlTestChunks 设为true，来开启校验.
     */
    assignUrlTestChunks: {
      type: Boolean,
      default: false,
    },
    /**
     * 未指定url的 testChunks，默认为true.
     */
    testChunks: {
      type: Boolean,
      default: true,
    },
    assignUrlEnableChunkUploadedCheckCallBackFunc: {
      type: Boolean,
      default: false,
    },
    /**
     * 校验分片上传响应回调
     */
    checkChunkUploadedByResponseFunc: {
      type: Function,
    },
    /**
     * 注：如果传入的url已实现分片上传，可将 assignUrlEnableComputeMD5 设为true，来开启md5计算.
     */
    assignUrlEnableComputeMD5: {
      type: Boolean,
      default: false,
    },
    /**
     * 注：如果传入的url已实现分片上传，可将 assignUrlEnableManuallyMerge 设为true，来开启分块合并处理.
     */
    assignUrlEnableManuallyMerge: {
      type: Boolean,
      default: false,
    },
    /**
     * 上传成功，分块合并处理函数.
     */
    chunkUploadedMergeFunc: {
      type: Function,
    },
    fileStatusText: {
      type: Object,
      default: () => {
        return {
          success: "成功",
          error: "失败",
          uploading: "上传中...",
          paused: "暂停",
          waiting: "等待",
        };
      },
    },
  },
  data() {
    return {
      viewModel: {
        controlData: {},
      },
      visible: false,
      loadInfoDone: false,
      uploaderFiles: []
    };
  },
  computed: {
    opts() {
      const url = this.url
        ? this.url
        : `${process.env.VUE_APP_BASE_API}/attachment/chunkUpload2?dataKey=${
            this.dataKey
          }&type=${this.type}&bizType=${this.bizType || ""}&isTemp=${this.isTemp}`;
      const h = {};
      h[this.$framework.constant.token.csrfToken] = getCsrfToken();
      return _.extend(
        {},
        {
          /**
           * config doc：https://github.com/simple-uploader/Uploader#configuration
           */
          target: url,
          maxChunkRetries: 3,
          query: this.query,
          headers: _.extend({}, { headers: h }, this.headers),
        },
        this.chunkOpts
      );
    },
    chunkOpts() {
      const chunkOpts = {
        chunkSize: this.url ? this.assignUrlChunkSize : this.chunkSize,
        testChunks: this.url ? this.assignUrlTestChunks : this.testChunks,
      };
      if (
        !this.url ||
        (this.url && this.assignUrlEnableChunkUploadedCheckCallBackFunc)
      ) {
        // 校验分片上传响应回调
        chunkOpts.checkChunkUploadedByResponse = (chunk, res) => {
          if (this.checkChunkUploadedByResponseFunc) {
            return this.checkChunkUploadedByResponseFunc(chunk, res);
          }
          const { success, parameters } = JSON.parse(res);
          if (success) {
            const { chunks } = parameters || {};
            return (chunks || []).indexOf(chunk.offset + 1) >= 0;
          }
          return false;
        };
      }
      return chunkOpts;
    },
    isTemp() {
      return this.dataKey === "qm-uploader-temp-data-key";
    }
  },
  watch: {
    value(visible) {
      this.visible = visible;
    },
  },
  methods: {
    getUploader() {
      const uploader = this.$refs.uploader.uploader;
      return uploader;
    },
    allRemove() {
      const uploader = this.$refs.uploader.uploader;
      uploader.cancel();
      this.$emit("cancel");
    },
    allPause() {
      const uploader = this.$refs.uploader.uploader;
      (uploader.fileList || []).forEach((file) => {
        file.pause();
      });
    },
    allUpload() {
      const uploader = this.$refs.uploader.uploader;
      (uploader.fileList || []).forEach((file) => {
        file.resume();
      });
    },
    cancel() {
      this.clearUploaderFiles();
      this.$emit("input", false);
      this.$emit("cancel");
    },
    clearUploaderFiles() {
      const uploader = this.getUploader();
      uploader.cancel();
      if ((uploader.fileList || []).length > 0) {
        this.uploaderFiles = [];
      }
    },
    beforeUpload: function (file) {
      const uploader = this.$refs.uploader.uploader;
      if (this.limit && (uploader.fileList || []).length > this.limit) {
        file.ignored = true;
        return;
      }
      if ((uploader.fileList || []).length > 0) {
        this.uploaderFiles = uploader.fileList;
      }
      const fileType = this.fileType;
      const fileSize = this.fileSize;

      if (fileType) {
        // 控制上传的大小及格式
        const isAllowType =
          fileType.indexOf(
            file.name.toLowerCase().substring(file.name.lastIndexOf(".") + 1)
          ) !== -1;
        if (!isAllowType) {
          this.$message.error("上传文件只能是 " + fileType + " 格式!");
          file.ignored = true;
          return;
        }
      }
      if (fileSize) {
        const isAllowSize = file.size / 1024 / 1024 < fileSize;

        if (!isAllowSize) {
          this.$message.error("上传文件大小不能超过 " + fileSize + "MB!");
          file.ignored = true;
          return;
        }
      }

      if (!this.url || (this.url && this.assignUrlEnableComputeMD5)) {
        // 文件添加的时候暂停文件的上传，计算md5值
        file.pause();
        // 计算MD5值,作为文件的唯一标识
        this.computeMD5(file);
      }
    },
    computeMD5(file) {
      let blobSlice =
          File.prototype.slice ||
          File.prototype.mozSlice ||
          File.prototype.webkitSlice,
        chunks = file.chunks.length || Math.ceil(file.size / this.chunkSize),
        currentChunk = 0,
        spark = new SparkMD5.ArrayBuffer(),
        fileReader = new FileReader();
      let time = new Date().getTime();
      // 文件状态为"计算md5..."
      file.cmd5 = true;
      fileReader.onload = (e) => {
        // Append array buffer
        spark.append(e.target.result);
        currentChunk++;
        if (currentChunk < chunks) {
          loadNext();
        } else {
          const md5 = spark.end();
          console.log(
            `MD5计算完成：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${
              file.size
            } 用时：${new Date().getTime() - time} ms`
          );
          // 释放缓存
          spark.destroy();
          // 将文件md5赋值给文件唯一标识
          file.uniqueIdentifier = md5;
          // 取消计算md5状态
          file.cmd5 = false;
        }
      };
      fileReader.onerror = () => {
        console.warn("oops, something went wrong.");
        file.cancel();
      };
      let loadNext = () => {
        let start = currentChunk * this.chunkSize,
          end =
            start + this.chunkSize >= file.size
              ? file.size
              : start + this.chunkSize;
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
      };
      loadNext();
    },
    handleSuccess: function (rootFile, file, response, chunk) {
      if (!this.url || (this.url && this.assignUrlEnableManuallyMerge)) {
        this.uploadSuccessMergeHandle(rootFile, file, response, chunk);
      } else {
        this.uploadSuccessDefaultHandle(response, file);
      }
    },
    /**
     * 上传成功，块合并处理
     */
    uploadSuccessMergeHandle(rootFile, file, response, chunk) {
      if (this.chunkUploadedMergeFunc) {
        this.chunkUploadedMergeFunc(rootFile, file, response, chunk);
        return;
      } else {
        const { success, parameters } = JSON.parse(response) || {};
        if (success) {
          request({
            url: "/attachment/merge",
            method: "post",
            data: {
              chunks: file.chunks.length,
              modelJson: JSON.stringify({
                fileSize: file.size,
                fileName: file.name,
                extension: file.ext,
                md5Key: (parameters || {}).md5Key,
                fileType: file.fileType,
                dataKey: this.dataKey,
                type: this.type,
                bizType: this.bizType,
                isTemp: this.isTemp,
              }),
            },
          }).then((mergeRes) => {
            const { success, parameters } = mergeRes || {};
            if (success) {
              const attachment = (parameters || {}).file;
              this.fileList.push(attachment);
              this.$emit("file-success", attachment);
            } else {
              this.$message.error(attachment.message || "文件上传失败");
            }
          });
        } else {
          this.$message.error(attachment.message || "文件上传失败");
        }
      }
    },
    /**
     * 上传成功，默认处理
     */
    uploadSuccessDefaultHandle(response, file) {
      const attachment = JSON.parse(response);
      if (attachment && attachment.success === false) {
        this.$message.error(attachment.message || "文件上传失败");
      } else {
        this.fileList.push(attachment);
        this.$emit("file-success", attachment);
      }
    },
    // fetchData() {
    //   return new Promise((rs, rj) => {
    //     request({
    //       url: "/attachment/getUploadViewModel",
    //       method: "post",
    //       data: { dataKey: this.dataKey, type: this.type },
    //     })
    //       .then((vm) => {
    //         this.viewModel = _.extend({}, this.viewModel, vm || {});
    //         this.loadInfoDone = true;
    //         rs();
    //       })
    //       .catch((e) => {
    //         rj();
    //       });
    //   });
    // },
  },
};
</script>

<style scoped>
.qm-uploader {
  display: flex;
  flex-flow: column nowrap;
  height: 100%;
}
.qm-uploader-list {
  flex: 1;
  margin-bottom: 12px;
  overflow-y: auto;
}
.qm-uploader-select-btn {
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  color: #ffffff;
  background-color: #1890ff;
  border-color: #1890ff;
  -webkit-appearance: none;
  text-align: center;
  box-sizing: border-box;
  outline: none;
  margin: 0;
  margin-right: 8px;
  transition: 0.1s;
  font-weight: 500;
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  font-size: 12px;
  padding: 9px 15px;
  border-radius: 3px;
}
.qm-uploader-select-btn:hover {
  background: #46a6ff;
  border-color: #46a6ff;
  color: #ffffff;
}
</style>
